package cn.wizzer.app.user.modules.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.wizzer.app.crown.modules.models.enums.SettleStatusEnum;
import cn.wizzer.app.sys.modules.models.SysParam;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.sys.modules.services.SysSensitiveService;
import cn.wizzer.app.user.modules.models.GroupMember;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.UserAccount;
import cn.wizzer.app.user.modules.models.constant.UserConstant;
import cn.wizzer.app.user.modules.models.enums.*;
import cn.wizzer.app.user.modules.models.vo.UserAccountVo;
import cn.wizzer.app.user.modules.models.vo.UserInvitationVo;
import cn.wizzer.app.user.modules.models.vo.UserTeamVo;
import cn.wizzer.app.user.modules.services.GroupInfoService;
import cn.wizzer.app.user.modules.services.GroupMemberService;
import cn.wizzer.app.user.modules.services.UserLogService;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.app.user.modules.services.account.UserAccountService;
import cn.wizzer.app.user.util.TokenUtil;
import cn.wizzer.framework.base.model.enums.BizExceptionEnum;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.*;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.plugins.wkcache.annotation.CacheRemoveAll;
import org.nutz.plugins.wkcache.annotation.CacheResult;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = UserService.class)
public class UserServiceImpl extends BaseServiceImpl<User> implements UserService {

    private static final Log log = Logs.get();

    public UserServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private TokenUtil tokenUtil;

    @Inject
    private UserAccountService accountService;

    @Inject
    private RedisService redisService;

    @Reference
    @Inject
    private SysSensitiveService sysSensitiveService;

    @Inject
    private CommonDBService dbService;

    @Inject
    private UserLogService userLogService;

    @Inject
    @Reference
    private SysParamService sysParamService;

    @Inject
    private UserAccountRecordService accountRecordService;

    @Inject
    private GroupInfoService groupInfoService;

    @Inject
    private GroupMemberService groupMemberService;

    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public void insertUser(User user, String inviteCode) {
        // 判断用户名是否重复
        if (this.count(Cnd.where("userName", "=", user.getUserName())) > 0) {
            Result.breakWith(BizExceptionEnum.USER_NAME_EXISTS);
        }
        // 判断手机是否重复（后台添加用户不输入手机号，要判空）
        if (null != user.getPhonePrefix() && null != user.getPhone()) {
            if (this.count(Cnd.where("phonePrefix", "=", user.getPhonePrefix()).and("phone", "=", user.getPhone())) > 0) {
                Result.breakWith(BizExceptionEnum.PHONE_EXIST);
            }
        }

        if (null != user.getPassword()) {
            String salt = R.UU32();
            user.setSalt(salt);
            user.setPassword(new Sha256Hash(user.getPassword(), ByteSource.Util.bytes(salt), 1024).toHex());
        }
        String path;
        String userId = R.UU32();
        //全局参数
        SysParam sysParam = sysParamService.fetch();
        if (sysParam.isNeedInviteCode()) {
            if (Strings.isBlank(inviteCode)) {
                Result.breakWith(BizExceptionEnum.INVITE_CODE_NOT_BE_NULL);
            }
        }
        // 邀请码不为空，查出关联的用户
        if (Strings.isNotBlank(inviteCode)) {
            User inviteUser = this.fetch(Cnd.where("inviteCode", "=", inviteCode));
            if (inviteUser == null) {
                Result.breakWith(BizExceptionEnum.ERROR_INVITE_CODE);
            }
            // 关联邀请人
            user.setInviter(inviteUser.getId());
            //region 仅保留10级已注释
//            String inviterPath = inviteUser.getPath();
//            String[] split = inviterPath.split("/");
//            if(split.length >= 10){
//                inviterPath = "";
//                for (int i = split.length - 9; i < split.length; i++) {
//                    if(Strings.isBlank(inviterPath)){
//                        inviterPath = split[i];
//                    }else {
//                        inviterPath = inviterPath + "/" + split[i];
//                    }
//                }
//            }
//            path = inviterPath + "/" + userId;
            //endregion
            path = inviteUser.getPath() + "/" + userId;
            //设置层级，为邀请人的层级 + 1
            user.setLayer(inviteUser.getLayer() + 1);
        } else {
            path = userId;
        }
        user.setId(userId);
        user.setPath(path);
        // 生成该用户自己的邀请码
        String myInviteCode;
        do {
            myInviteCode = InviteCodeUtil.getInviteCode();
        } while (this.count(Cnd.where("inviteCode", "=", myInviteCode)) > 0);
        user.setInviteCode(myInviteCode);
        try {
            this.insert(user);
        } catch (Exception e) {
            log.error("注册失败：" + e);
            Result.breakWith(BizExceptionEnum.REGISTER_FAILED);
        }
    }


    @Override
    public List<UserAccountVo> userAccountVoList(String key, String currencyId) {
        Sql sql = Sqls.create("SELECT a.balance,u.id AS userId,u.`userName`,u.`realName` FROM user_account a ");
        Sql joinSql = Sqls.create("LEFT JOIN USER u ON u.id = a.userId $condition ");
        Cnd cnd = Cnd.NEW();
        if (Strings.isNotBlank(key)) {
            cnd.and(Cnd.exps("u.userName", "=", key));
        }
        cnd.and("a.currencyId", "=", currencyId);
        joinSql.setCondition(cnd);
        sql.appendSourceSql(joinSql.toString());

        return dbService.list(UserAccountVo.class, sql.toString());
    }


    @Override
    public Pagination userListNew(String conditionData, String userKeyword, String inviterKeyword, String[] searchDate, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("SELECT id,isOnline,visitor,disabled,sessionDisabled,nickName,userName," +
                "email,inviteCode,layer,realName,phonePrefix,phone,multiple,inside,transRestriction,effectQuota," +
                "inviter AS i,(SELECT nickName FROM user WHERE id = i) AS inviter," +
                "(SELECT COUNT(*) FROM user WHERE inviter = u.id) AS invitedNum," +
                "attendant,ip,lastLoginAt,loginDuration,withdrawFreeTimes,ctAt,u.remark FROM user AS u" +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user " +
                "$condition ");
        Cnd cnd = Cnd.where("delFlag", "=", 0);
        if (Strings.isNotBlank(conditionData)) {
            String[] split = conditionData.split(",");
            for (String col : split) {
                String colName = UserColEnum.getValByName(col);
                if (Strings.isNotBlank(colName)) {
                    // 默认不显示游客所以值为0，其他条件是1
                    if (UserColEnum.VISITOR.getVal().equals(colName)) {
                        cnd.and(colName, "=", 0);
                    } else {
                        cnd.and(colName, "=", 1);
                    }
                }
            }
        }
        // 查询用户列表
        if (Strings.isNotBlank(userKeyword)) {
            cnd.and(Cnd.exps("userName", "=", userKeyword)
                    .or("nickName", "=", userKeyword)
                    .or("ip", "=", userKeyword)
                    .or("phone", "like", "%" + userKeyword + "%")
                    .or("realName", "=", userKeyword)
            );
        }
        // 查询下级邀请人
        else if (Strings.isNotBlank(inviterKeyword)) {
            // 根据搜索关键字查出邀请人id
            User inviter = this.fetch(
                    Cnd.where(Cnd.exps("userName", "=", inviterKeyword)
                            .or("ip", "=", inviterKeyword)
                            .or("phone", "=", inviterKeyword)
                    ));
            // 查询path为该id开头的被邀请人id集合
            if (null != inviter) {
                cnd.and("inviter", "=", inviter.getId());
            }
        }
        if (null != searchDate && searchDate.length != 0) {
            cnd.and("ctAt", ">=", Times.d2TS(Times.D(searchDate[0])));
            cnd.and("ctAt", "<=", Times.d2TS(Times.D(searchDate[1])));
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("ORDER BY lastLoginAt DESC");
        countSql.setCondition(cnd);
        return dbService.listPage(User.class, pageNumber, pageSize, sql.toString(), countSql.toString());
    }


    @Override
    public Pagination inviteList(String appid, Integer level, String timezone, long startTime, long endTime, int pageNumber, int pageSize) {
        User user = this.fetch(appid);
        Sql sql = Sqls.create("SELECT id, nickName, userName, sign, ctAt, layer as level " +
                "FROM user " +
                "$condition ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user " +
                "$condition ");
        Cnd cnd = Cnd.where("path", "LIKE", "%" + user.getPath() + "/%");
        if (level != null && level > 0) {
            cnd.and("layer", "=", user.getLayer() + level);
        }
        if (startTime > 0 && endTime > 0) {
            // 根据timezone把start和end转成系统所在时区的时间
            cnd.and("ctAt", ">=", startTime);
            cnd.and("ctAt", "<=", endTime);
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("order by ctAt desc");
        countSql.setCondition(cnd);
        Pagination pagination = dbService.listPage(UserTeamVo.class, pageNumber, pageSize, sql.toString(), countSql.toString());
        List<UserTeamVo> userTeamVoList = pagination.getList(UserTeamVo.class);
        if (userTeamVoList.size() > 0) {
            //下级给予自己的返佣金额总数
            Sql sumSql = Sqls.create("SELECT IFNULL(SUM(rebateMoney), 0) " +
                    "FROM user_rebate WHERE toUser = @toUser AND fromUser = @fromUser ");
            sumSql.setParam("toUser", appid);
            sumSql.setCallback(Sqls.callback.str());
            // 查询下线个人充值金额，状态为SUCCESS（排除后台充值）
            Sql rechargeSql = Sqls.create("SELECT IFNULL(SUM(amount * c.rate), 0) " +
                    "FROM user_recharge AS r " +
                    "LEFT JOIN sys_currency AS c ON r.currencyId = c.id " +
                    "WHERE userId = @userId AND status = @status AND channel != @channel")
                    .setParam("status", RechargeStatusEnum.SUCCESS)
                    .setParam("channel", PayChannel.PLAT_FORM);
            rechargeSql.setCallback(Sqls.callback.str());
            // 查询下线个人提现，状态为SUCCESS（排除后台扣钱）
            Sql withdrawSql = Sqls.create("SELECT IFNULL(SUM(amount * c.rate), 0) " +
                    "FROM user_withdraw AS w " +
                    "LEFT JOIN sys_currency AS c ON w.currencyId = c.id " +
                    "WHERE userId = @userId AND status = @status AND channel != @channel")
                    .setParam("status", WithdrawStatusEnum.SUCCESS)
                    .setParam("channel", WithdrawChannel.PLATFORM);
            withdrawSql.setCallback(Sqls.callback.str());
            // 查询下线个人投资，状态为SETTLED
            Sql investSql = Sqls.create("SELECT IFNULL(SUM(amount), 0) FROM crown_order AS o " +
                    "WHERE userId = @userId AND status = @status")
                    .setParam("status", SettleStatusEnum.SETTLED);
            investSql.setCallback(Sqls.callback.str());
            // 循环统计每个下线
            for (UserTeamVo userTeamVo : userTeamVoList) {
                sumSql.setParam("fromUser", userTeamVo.getId());
                rechargeSql.setParam("userId", userTeamVo.getId());
                withdrawSql.setParam("userId", userTeamVo.getId());
                investSql.setParam("userId", userTeamVo.getId());
                this.dao().execute(sumSql);
                this.dao().execute(rechargeSql);
                this.dao().execute(withdrawSql);
                this.dao().execute(investSql);
                // 收益
                userTeamVo.setLevel(userTeamVo.getLevel() - user.getLayer());
                // 层级
                userTeamVo.setEarned(new BigDecimal(sumSql.getString()));
                // 充值
                userTeamVo.setTotalRecharge(new BigDecimal(rechargeSql.getString()).setScale(2, RoundingMode.HALF_UP));
                // 提现
                userTeamVo.setTotalWithdraw(new BigDecimal(withdrawSql.getString()).setScale(2, RoundingMode.HALF_UP));
                // 投资
                userTeamVo.setTotalInvest(new BigDecimal(investSql.getString()).setScale(2, RoundingMode.HALF_UP));
            }
        }
        return pagination;
    }

    @Override
    @Aop(TransAop.READ_COMMITTED)
    public void updateUser(User user) {
        // 处理登录密码
        if (Strings.isNotBlank(user.getPassword())) {
            String newPwd = new Sha256Hash(user.getPassword(), ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
            user.setPassword(newPwd);
        }
        // 处理交易密码
        if (Strings.isNotBlank(user.getTransactionPwd())) {
            String newPwd = new Sha256Hash(user.getTransactionPwd(), ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
            user.setTransactionPwd(newPwd);
        }
        this.updateIgnoreNull(user);
    }

    @Override
    public Result login(String userName, String password, EquipmentEnum equipment, String ip) {
        userName = Strings.trim(userName);
        // 错误次数限制
        String timesKey = UserConstant.REDIS_KEY_LOGIN_ERR + userName;
        String times = redisService.get(timesKey);
        if ("0".equals(times)) {
            return Result.error(BizExceptionEnum.FAIL_EXCESSIVE);
        }
        User user;
        if (userName.contains("@")) {
            user = this.fetch(Cnd.where("email", "=", userName));
        } else {
            user = this.fetch(Cnd.where("userName", "=", userName).or("phone", "=", userName));
        }
        if (user == null) {
            return Result.error(BizExceptionEnum.USER_NOT_EXIST);
        }
        String encryption = new Sha256Hash(password, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();

        if (!sysParamService.fetch().isDebug()) {
            if (!encryption.equals(user.getPassword())) {
                if (Strings.isNotBlank(times)) {
                    int i = Integer.parseInt(times);
                    times = i - 1 + "";
                } else {
                    times = "4";
                }
                redisService.set(timesKey, times);
                redisService.expire(timesKey, (int) (Times.ams(Times.afterDay()) / 1000 - Times.getTS()));
                if ("0".equals(times)) {
                    return Result.error(BizExceptionEnum.FAIL_EXCESSIVE);
                } else {
                    return Result.error(BizExceptionEnum.PWD_ERROR).addData(times);
                }
            } else {
                // 登录成功，登录错误次数清零
                redisService.del(timesKey);
            }
        }

        if (user.isDisabled()) {
            return Result.error(BizExceptionEnum.USER_DISABLED);
        }

        // 校验通过生成token返回用户信息
        NutMap resmap = new NutMap();

        // 原来有登录要踢掉
        redisService.del(("api_token:" + user.getId()));
        try {
            // generateToken 本身自带时间期限，用redis 自带的，不需要自己处理
            String token = tokenUtil.generateToken(user.getId());
            resmap.addv("token", token);
            resmap.addv("appid", user.getId());
            resmap.addv("nickName", user.getNickName());
            resmap.addv("userName", user.getUserName());
            resmap.addv("email", user.getEmail());
            resmap.addv("sign", user.getSign());
            resmap.addv("inviteCode", user.getInviteCode());
            resmap.addv("attendant", user.isAttendant());
            resmap.addv("inside", user.isInside());
            resmap.addv("hasTradePwd", Strings.isNotBlank(user.getTransactionPwd()));
            resmap.addv("realName", user.getRealName());
            resmap.addv("headPicture", user.getHeadPicture());
            // 返回给前端，第几次登录
            resmap.addv("loginTimes", user.getLoginTimes() + 1);
            if (Strings.isNotBlank(user.getPhonePrefix()) && Strings.isNotBlank(user.getPhone())) {
                resmap.addv("phone", user.getPhonePrefix() + "-" + user.getPhone());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //更新登录时间
        this.update(Chain.make("lastLoginAt", Times.getTS())
                        .add("loginTimes", user.getLoginTimes() + 1)
                        .add("isOnline", 1)
                        .add("ip", ip),
                Cnd.where("id", "=", user.getId()));

        return Result.success(BizExceptionEnum.LOGIN_SUCCESS, resmap);
    }

    @Override
    public void offLine() {
        StringBuffer sb = new StringBuffer();
        sb.append("select id,loginTimes,loginDuration,lastLoginAt,visitor from user where isOnLine='1'");
        List<User> userList = dbService.list(User.class, sb.toString());
        for (User user : userList) {
            byte[] obj = redisService.get(("api_token:" + user.getId()).getBytes());
            if (obj == null) {
                // 游客账户token过期不会被再登录所以删掉数据
                if (!user.isVisitor()) {
                    //更新登录时间
                    this.update(Chain.make("lastLoginAt", Times.getTS())
                                    .add("isOnline", 0)
                                    .add("loginDuration", calculateLoginDuration(user)),
                            Cnd.where("id", "=", user.getId()));
                }
            }
        }
    }

    /**
     * 计算用户登录时长，按小时
     *
     * @param user
     * @return
     */
    @Override
    public BigDecimal calculateLoginDuration(User user) {
        //此次登录时长 = 当前时间 - 最近一次登录时间
        long nowLoginDuration = Times.getTS() - user.getLastLoginAt();
        //按小时计算，此次登录时长 / 秒 / 分 + 历史登录时长
        return BigDecimal.valueOf(nowLoginDuration).divide(BigDecimal.valueOf(60).multiply(BigDecimal.valueOf(60)), 2, RoundingMode.DOWN).add(user.getLoginDuration());
    }

    /**
     * 新密码验证
     *
     * @param user
     * @param newPwd
     * @param newTransactionPwd
     */
    @Override
    public void newPwdValidation(User user, String newPwd, String newTransactionPwd) {
        //旧登录密码
        String oldPwd = user.getPassword();
        //旧交易密码
        String oldTransactionPwd = user.getTransactionPwd();
        if (Strings.isNotBlank(newPwd)) {
            //新登录密码不能与旧登录密码相同
            if (newPwd.equals(oldPwd)) {
                Result.breakWith(BizExceptionEnum.NEW_PWD_CAN_NOT_SAME_TO_OLD_PWD);
            }
//            if (Strings.isNotBlank(oldTransactionPwd)) {
//                //新登录密码不能与交易密码相同
//                if (newPwd.equals(oldTransactionPwd)) {
//                    Result.breakWith(BizExceptionEnum.LOGIN_PWD_CAN_NOT_SAME_TO_TRANSACTION_PWD);
//                }
//            }
        }
        if (Strings.isNotBlank(newTransactionPwd)) {
            if (Strings.isNotBlank(oldTransactionPwd)) {
                //新交易密码不能与旧交易密码相同
                if (newTransactionPwd.equals(oldTransactionPwd)) {
                    Result.breakWith(BizExceptionEnum.NEW_PWD_CAN_NOT_SAME_TO_OLD_PWD);
                }
            }
            //新交易密码不能与登录密码相同
//            if (newTransactionPwd.equals(oldPwd)) {
//                Result.breakWith(BizExceptionEnum.LOGIN_PWD_CAN_NOT_SAME_TO_TRANSACTION_PWD);
//            }
        }
    }

    /**
     * 根据用户id集合获取用户信息
     *
     * @param ids
     * @return
     */
    @Override
    public Map<String, User> findByIds(List<String> ids) {
        List<User> users = this.query(Cnd.where("id", "IN", ids));
        return users.stream().collect(Collectors.toMap(User::getId, user -> user));
    }

    @Override
    public Map<String, String> usersToMapJSONString(List<User> users) {
        return users.stream().collect(Collectors.toMap(User::getId, JSONObject::toJSONString));
    }

    /**
     * 我的邀请
     *
     * @param userId
     * @return
     */
    @Override
    public Map myInvitation(String userId) {
        User user = this.fetch(userId);
        BigDecimal sumAmount = accountRecordService.sumAmountByBizTypeAndUserId(BizTypeEnum.SUB_REBATE, userId);
        return Result.createMap("inviteCode", user.getInviteCode(),
                "earned", sumAmount,
                "userCount", this.count(Cnd.where("inviter", "=", userId))
        );
    }

    /**
     * 邀请榜
     *
     * @return
     */
    @Override
    @CacheResult
    public List invitationList(Integer limit) {
        if (limit == null || limit <= 0) {
            limit = 100;
        }
        long current = System.currentTimeMillis();
        long zero = current / (1000 * 3600 * 24) * (1000 * 3600 * 24) - TimeZone.getDefault().getRawOffset();
        Sql sql = Sqls.create("SELECT u.nickName, s.toUser as userId, s.earned,  " +
                "(SELECT COUNT(1) FROM user WHERE inviter = s.toUser and ctAt < @zero ) as invitedNum " +
                "FROM (SELECT SUM(rebateMoney) as earned, toUser " +
                "       FROM user_rebate " +
                "       group by toUser) AS s " +
                "LEFT JOIN user AS u ON s.toUser = u.id " +
                "ORDER BY s.earned DESC " +
                "LIMIT @limit")
                .setParam("zero", zero / 1000)
                .setParam("zeroMill", zero);
        sql.setParam("limit", limit);
        List<UserInvitationVo> list = dbService.list(UserInvitationVo.class, sql.toString());
        // nickName添加星号
        list.forEach(this::nickNameMosaic);
        // 如果数据不到100条插入假数据
        if (list.size() < limit) {
            list = this.mockInvitation(list, limit);
        }
        return list;
    }

    private void nickNameMosaic(UserInvitationVo vo) {
        // 取第一个和最后一个字符其他都是*
        String nickName = vo.getNickName();
        String f = nickName.substring(0, 1);
        String l = nickName.substring(nickName.length() - 1);
        String m = nickName.substring(1, nickName.length() - 1);
        vo.setNickName(f + m.replaceAll(".", "*") + l);
    }

    /**
     * 处理假数据
     *
     * @param list
     * @param limit
     */
    private List<UserInvitationVo> mockInvitation(List<UserInvitationVo> list, Integer limit) {
        // 排行榜数据不够从redis中取出假数据
        Set<String> set = redisService.zrange("inviteMock", 0, -1);
        //  如果redis数据为空，初始化
        List<UserInvitationVo> mockList;
        if (set.isEmpty()) {
            mockList = this.initInviteMock();
        }
        // 如果redis已有数据，随机更新
        else {
            // 更新
            mockList = set.stream().map(str -> JSONObject.parseObject(str, UserInvitationVo.class)).collect(Collectors.toList());
            // 更新排行榜时自定义的增长量 ("1", "10&10.00": 1阶段，邀请人数随机数10+&获得收益随机数70+)
            HashMap<Integer, String> riseMap = new HashMap<>(4);
            riseMap.put(0, "10&70.00");
            riseMap.put(1, "100&150.00");
            riseMap.put(2, "200&300.00");
            riseMap.put(3, "10&25.00");
            for (int i = 0; i < mockList.size(); i++) {
                this.randomRise(mockList.get(i), i, riseMap);
            }
        }
        // 混合真数据，集合按earned倒序排序，取前100
        list.addAll(mockList);
        list = list.stream()
                .sorted(Comparator.comparing(UserInvitationVo::getEarned).reversed())
                .limit(limit)
                .collect(Collectors.toList());
        return list;
    }

    /**
     * 初始化排行榜
     *
     * @return
     */
    private List<UserInvitationVo> initInviteMock() {
        UserInvitationVo vo = new UserInvitationVo();
        // 初始化排行榜时自定义的增长量 ("1", "200&500.00": 1阶段，邀请人数随机数200+&获得收益随机数500+)
        HashMap<Integer, String> riseMap = new HashMap<>(4);
        riseMap.put(0, "200&5500.00");
        riseMap.put(1, "450&1100.00");
        riseMap.put(2, "20&515.00");
        riseMap.put(3, "8&20.00");
        // 昵称字符
        HashMap<Integer, String> nameMap = new HashMap<>(4);
        nameMap.put(0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
        nameMap.put(1, "0123456789");
        nameMap.put(2, "ชัยชนะเป็นที่หนึ่งเย็นเป็นน้ำแข็งตัวการ์ตูนในเรื่องมิกกี้เมาซ์เจ้านายสะระแหน่มีกลิ่นหอมจุดสำคัญจุดรวมศูนย์กลาง");
        nameMap.put(3, "बॉलकापरमेश्वररोनाल्डोमेसीमुटनमासालासूर्यकापुत्राभारतकेसबसेधनीपुरुषप्रोफेसरडिल्लीविश्वविद्यालयशुद्धऔरसामान्य");
        String nickName;
        for (int i = 0; i < 100; i++) {
            vo.setUserId(R.UU32());
            // 生成昵称(英文、泰文、印度文随机)
            nickName = RandomUtil.randomString(nameMap.get(RandomUtil.randomInt(nameMap.size())), RandomUtil.randomInt(5, 9));
            vo.setNickName(nickName);
            // 昵称打码
            this.nickNameMosaic(vo);
            // 初始化排行榜邀请人、收益数据
            this.randomRise(vo, i, riseMap);
            redisService.zadd("inviteMock", vo.getEarned().doubleValue(), JSONUtil.toJSONString(vo));
        }
        Set<String> invite = redisService.zrange("inviteMock", 0, -1);
        return invite.stream().map(str -> JSONObject.parseObject(str, UserInvitationVo.class)).collect(Collectors.toList());
    }

    /**
     * 收入、邀请人数随机增长（初始化排行榜时增长值与后续更新排行榜时增长值可以自由调节幅度）
     *
     * @param vo      排行榜vo对象
     * @param i       处于排行榜的位置
     * @param riseMap 增长值Map
     */
    private void randomRise(UserInvitationVo vo, int i, HashMap<Integer, String> riseMap) {
        int inviteNum;
        double earned;
        String s;
        // 第一段邀请1000+人
        if (i < 13) {
            s = riseMap.get(0);
            inviteNum = 1584 + RandomUtil.randomInt(Integer.parseInt(s.split("&")[0]));
            earned = 18000.55 + RandomUtil.randomDouble(Double.parseDouble(s.split("&")[1]));
        }
        // 第二段邀请100-500+人
        else if (i < 40) {
            s = riseMap.get(1);
            inviteNum = 177 + RandomUtil.randomInt(Integer.parseInt(s.split("&")[0]));
            earned = 1900.8 + RandomUtil.randomDouble(Double.parseDouble(s.split("&")[1]));
        }
        // 第三段邀请10-30+人
        else if (i < 90) {
            s = riseMap.get(2);
            inviteNum = 10 + RandomUtil.randomInt(Integer.parseInt(s.split("&")[0]));
            earned = 330 + RandomUtil.randomDouble(Double.parseDouble(s.split("&")[1]));
        }
        // 第四段邀请10个以内
        else {
            s = riseMap.get(3);
            inviteNum = 1 + RandomUtil.randomInt(Integer.parseInt(s.split("&")[0]));
            earned = 10 + RandomUtil.randomDouble(Double.parseDouble(s.split("&")[1]));
        }
        vo.setEarned(BigDecimal.valueOf(earned).setScale(2, RoundingMode.DOWN));
        vo.setInvitedNum(inviteNum);
    }

    @Override
    @CacheRemoveAll
    public void clearCache() {

    }

    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public Result visitorLogin(String ip) {
        User user = new User();
        // 设置游客用户名、昵称
        String name = "visitor" + Id.createCode().substring(4);
        String userId = R.UU32();
        user.setId(userId);
        user.setUserName(name);
        user.setNickName(name);
        user.setVisitor(true);
        user.setOnline(true);
        user.setInside(true);
        user.setIp(ip);
        // 设置随机邮箱
        user.setEmail(name + "@ballgold365.com");
        user.setLastLoginAt(Times.getTS());
        user.setLoginTimes(1L);
        // 插入数据库
        this.insert(user);
        // 设置默认账户金额
        UserAccount userAccount = new UserAccount();
        userAccount.setBalance(BigDecimal.valueOf(1000));
        userAccount.setCtAt(System.currentTimeMillis());
        userAccount.setUserId(userId);
        userAccount.setCurrencyId("default");
        accountService.insert(userAccount);
        NutMap resmap = new NutMap();
        // 生成token
        try {
            String token = tokenUtil.generateToken(user.getId());
            resmap.addv("token", token);
            resmap.addv("appid", user.getId());
            resmap.addv("nickName", user.getNickName());
            resmap.addv("userName", user.getUserName());
            resmap.addv("email", user.getEmail());
        } catch (IOException | ClassNotFoundException e) {
            log.error("游客登录token处理bug：" + e.getMessage());
        }
        return Result.success(BizExceptionEnum.LOGIN_SUCCESS, resmap);
    }


    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public User attendTemp(String appid, String ip) {
        User user = new User();
        // 设置游客用户名、昵称
        String name = "visitor" + Id.createCode().substring(4);
        user.setId(appid);
        user.setUserName(name);
        user.setNickName(name);
        user.setVisitor(true);
        user.setOnline(true);
        user.setIp(ip);
        // 设置随机邮箱
        user.setLastLoginAt(Times.getTS());
        user.setLoginTimes(1L);
        // 插入数据库
        this.insert(user);
        return user;
    }

    @Override
    public void deleteVisitor(String id) {
        // 查出该游客账号所在的群、群成员记录
        List<GroupMember> list = groupMemberService.query(Cnd.where("userId", "=", id));
        // 转换成groupIdList
        List<String> groupIdList = list.stream().map(GroupMember::getGroupId).collect(Collectors.toList());
        // 删除user表中该条数据 以及关联的account、account_record、crown_order、user_exchange、user_bank_card、user_recharge、
        // 同时删除该临时账号与客服建立的私聊群 group_info group_member
        Sql sql = Sqls.create("delete from user where id = @userId;" +
                "delete from user_account where userId = @userId;" +
                "delete from user_account_record where userId = @userId;" +
                "delete from crown_order where userId = @userId;" +
                "delete from user_exchange where userId = @userId;" +
                "delete from user_bank_card where userId = @userId;" +
                "delete from user_recharge where userId = @userId;")
                .setParam("userId", id);
        if (!groupIdList.isEmpty()) {
            sql.appendSourceSql("delete from group_member where groupId IN (@groupIdList);")
                    .appendSourceSql("delete from group_info where id IN (@groupIdList)")
                    .setParam("groupIdList", groupIdList);
        }
        log.info("删除游客账号数据,userId：" + id);
        this.execute(sql);
    }


    @Override
    public Pagination userList(String keyword, int pageNumber, int pageSize) {
        Cnd cnd = Cnd.where("delFlag", "=", 0);
        // 查询用户列表
        if (Strings.isNotBlank(keyword)) {
            keyword = "%" + keyword + "%";
            cnd.and(Cnd.exps("nickName", "LIKE", keyword)
                    .or("userName", "LIKE", keyword)
                    .or("phone", "LIKE", keyword)
            );
        }
        return this.listPage(pageNumber, pageSize, cnd);
    }

    @Override
    public Map<String, BigDecimal> allSubReport(String appid, Integer level, long startTime, long endTime) {
        // 查询所有下线userId,inside
        User user = this.fetch(appid);
        Sql sql = Sqls.create("SELECT id,inside FROM user $condition");
        Cnd cnd = Cnd.where("path", "LIKE", "%" + user.getPath() + "/%");
        if (level != null && level > 0) {
            cnd.and("layer", "=", user.getLayer() + level);
        }
        if (startTime > 0 && endTime > 0) {
            cnd.and("ctAt", ">=", startTime);
            cnd.and("ctAt", "<=", endTime);
        }
        sql.setCondition(cnd);
        sql.appendSourceSql("order by ctAt desc");
        List<User> userList = this.listEntity(sql);
        // 过滤内部账号，userList转换成userIdList
        List<String> userIdList = userList.stream()
                .filter(u -> !u.isInside())
                .map(User::getId)
                .collect(Collectors.toList());
        // 统计所有下线返佣总额
        Sql sumSql = Sqls.create("SELECT IFNULL(SUM(rebateMoney), 0) FROM user_rebate WHERE toUser = @toUser ")
                .setParam("toUser", appid);
        sumSql.setCallback(Sqls.callback.str());
        this.dao().execute(sumSql);
        if (userIdList.size() > 0) {
            // 统计所有下线的充值总额
            Sql rechargeSql = Sqls.create("SELECT IFNULL(SUM(amount * c.rate), 0) " +
                    "FROM user_recharge AS r " +
                    "LEFT JOIN sys_currency AS c ON r.currencyId = c.id " +
                    "WHERE userId in (@userIdList) AND status = @status AND channel != @channel")
                    .setParam("status", RechargeStatusEnum.SUCCESS)
                    .setParam("channel", PayChannel.PLAT_FORM)
                    .setParam("userIdList", userIdList);
            rechargeSql.setCallback(Sqls.callback.str());
            this.dao().execute(rechargeSql);
            // 统计所有下线的提现总额
            Sql withdrawSql = Sqls.create("SELECT IFNULL(SUM(amount * c.rate), 0) " +
                    "FROM user_withdraw AS w " +
                    "LEFT JOIN sys_currency AS c ON w.currencyId = c.id " +
                    "WHERE userId in (@userIdList) AND status = @status AND channel != @channel")
                    .setParam("status", WithdrawStatusEnum.SUCCESS)
                    .setParam("channel", WithdrawChannel.PLATFORM)
                    .setParam("userIdList", userIdList);
            withdrawSql.setCallback(Sqls.callback.str());
            this.dao().execute(withdrawSql);
            // 统计所有下线的投资总额
            Sql investSql = Sqls.create("SELECT IFNULL(SUM(amount), 0) FROM crown_order AS o " +
                    "WHERE userId in (@userIdList) AND status = @status")
                    .setParam("status", SettleStatusEnum.SETTLED)
                    .setParam("userIdList", userIdList);
            investSql.setCallback(Sqls.callback.str());
            this.dao().execute(investSql);
            return Result.createMap("allSubRecharge", new BigDecimal(rechargeSql.getString()).setScale(2, RoundingMode.HALF_UP),
                    "allSubWithdraw", new BigDecimal(withdrawSql.getString()).setScale(2, RoundingMode.HALF_UP),
                    "allSubInvest", new BigDecimal(investSql.getString()).setScale(2, RoundingMode.HALF_UP),
                    "allSubEarned", new BigDecimal(sumSql.getString()).setScale(2, RoundingMode.HALF_UP)
            );
        } else {
            return Result.createMap("allSubRecharge", new BigDecimal(0),
                    "allSubWithdraw", new BigDecimal(0),
                    "allSubInvest", new BigDecimal(0),
                    "allSubEarned", new BigDecimal(sumSql.getString()).setScale(2, RoundingMode.HALF_UP)
            );
        }


    }

}
